﻿# ------------------------- #
# Virtual Machine Functions #
# ------------------------- #
Import-Module "$Env:dp0\Modules\Core\IC.Azure.Remote.psm1" -DisableNameChecking -Force

# Add virtual machines
function Add-VirtualMachines([System.Xml.XmlElement]$subscription)
{
	if (!(Should-Run($subscription.virtualMachines))) {return}
	foreach ($virtualMachine in $subscription.virtualMachines.virtualMachine)
	{
	    if (!(Should-Run($virtualMachine))) { continue }
        if ((Test-VirtualMachineInstanceCompleted $subscription $virtualMachine)) { continue }
		Add-VirtualMachineInstance $subscription $virtualMachine
        Add-VirtualMachineInstanceCompleted $subscription $virtualMachine
    }
}

# Remove virtual machines
function Remove-VirtualMachines([System.Xml.XmlElement]$subscription)
{
	if (!(Should-Run($subscription.virtualMachines))) {return}
    foreach ($virtualMachine in $subscription.virtualMachines.virtualMachine)
	{
	    if (!(Should-Run($virtualMachine))) { continue }
        if ((Test-VirtualMachineInstanceCompleted $subscription $virtualMachine)) { continue }
		Remove-VirtualMachineInstance $virtualMachine
	}
    foreach ($virtualMachine in $subscription.virtualMachines.virtualMachine)
	{
	    if (!(Should-Run($virtualMachine))) { continue }
        if ((Test-VirtualMachineInstanceCompleted $subscription $virtualMachine)) { continue }
		Remove-CloudService $virtualMachine
	}
    foreach ($virtualMachine in $subscription.virtualMachines.virtualMachine)
	{
	    if (!(Should-Run($virtualMachine))) { continue }
        if ((Test-VirtualMachineInstanceCompleted $subscription $virtualMachine)) { continue }
        $image = Get-VirtualMachineInstanceImage $virtualMachine
        if ($image.OS -eq "Windows")
        {
		    Remove-VirtualMachineInstanceWinRMCertificate $virtualMachine
        }
        elseif ($image.OS -eq "Linux")
        {
            Remove-VirtualMachineInstancePuttySession $subscription $virtualMachine
        }
	}
    foreach ($virtualMachine in $subscription.virtualMachines.virtualMachine)
	{
	    if (!(Should-Run($virtualMachine))) { continue }
        if ((Test-VirtualMachineInstanceCompleted $subscription $virtualMachine)) { continue }
		Remove-VirtualMachineInstanceDisks $virtualMachine
	}
}

# Start virtual machines
function Start-VirtualMachines([System.Xml.XmlElement]$subscription)
{
	if (!(Should-Run($subscription.virtualMachines))) {return}
	foreach ($virtualMachine in $subscription.virtualMachines.virtualMachine)
	{
	    if (!(Should-Run($virtualMachine))) { continue }
		Start-VirtualMachineInstance $virtualMachine
    }
}

# Stop virtual machines
function Stop-VirtualMachines([System.Xml.XmlElement]$subscription)
{
	if (!(Should-Run($subscription.virtualMachines))) {return}
    for ($i = $subscription.virtualMachines.virtualMachine.Count - 1; $i -ge 0; $i--)
	{
	    if (!(Should-Run($subscription.virtualMachines.virtualMachine[$i]))) { continue }
		Stop-VirtualMachineInstance $subscription.virtualMachines.virtualMachine[$i] $false $false
    }
}

# Add cloud service
function Add-CloudService($subscription, $virtualMachine)
{
	if ($virtualMachine.service.Length -gt 63) { throw "Virtual machine '$($virtualMachine.name)' service name '$($virtualMachine.service)' length cannot exceed 63 characters." }
    # Create cloud service, if it does not exist
	if ((Get-AzureService | ? {$_.ServiceName -eq $virtualMachine.service}) -eq $null)
	{
        $operation = New-AzureService -ServiceName $virtualMachine.service -Location (Get-VirtualNetworkLocation $subscription $virtualMachine.network.name)
		if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Cloud service '$($virtualMachine.service)' operation failed." }
    }
}

# Remove cloud service
function Remove-CloudService($virtualMachine)
{
	if ((Get-AzureService -WarningAction Ignore | ? {$_.ServiceName -eq $virtualMachine.service}) -ne $null -and (Get-AzureVM -ServiceName $virtualMachine.service -WarningAction Ignore) -eq $null)
	{
		Write-Information "Removing virtual machine '$($virtualMachine.name)' cloud service '$($virtualMachine.service)' ... " -NoNewLine $true
		Remove-AzureService -ServiceName $virtualMachine.service -Force -WarningAction Ignore
		Write-Text "done."
	}
	else
	{
		Write-Information "Removing virtual machine '$($virtualMachine.name)' cloud service '$($virtualMachine.service)' ... skipped."
	}
}

# Add virtual machine instance
function Add-VirtualMachineInstance($subscription, $virtualMachine)
{
	if (Get-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name -WarningAction Ignore)
	{
		Write-Information "Virtual machine '$($virtualMachine.name)' creating ... skipped."
	}
	else
	{
		Write-Information "Virtual machine '$($virtualMachine.name)' creating ... " -NoNewLine $true
		$adminUserName = $subscription.virtualMachines.adminUserName
		$password = $subscription.virtualMachines.password
        $image = Get-VirtualMachineInstanceImage $virtualMachine
		$diskName = Get-VirtualMachineInstanceDiskName $virtualMachine 0
		$diskLocation = Get-VirtualMachineInstanceDiskLocation $virtualMachine.disks $diskName
        Add-CloudService $subscription $virtualMachine
        $config = Create-VirtualMachineInstance $subscription $virtualMachine $image $diskName $diskLocation
		Add-VirtualMachineInstanceEndpoints $virtualMachine $config
        Add-VirtualMachineInstanceNetwork $virtualMachine $config
        Provision-VirtualMachineInstance $subscription $virtualMachine $config
		Write-Text "done."
        if ($image.OS -eq "Windows")
        {
		    Add-VirtualMachineInstanceWinRMCertificate $virtualMachine
            Attach-VirtualMachineInstanceDisksWindows $virtualMachine $adminUserName $password
        }
        elseif ($image.OS -eq "Linux")
        {
            Add-VirtualMachineInstancePuttySession $subscription $virtualMachine
            Attach-VirtualMachineInstanceDisksLinux $subscription $virtualMachine
        }
        Associate-VirtualMachineNetworkSecurityGroup $virtualMachine
	}
    Assign-VirtualMachineInstanceResourceGroup $virtualMachine
	Execute-VirtualMachineInstanceScripts $subscription $virtualMachine
}

# Remove virtual machine instance
function Remove-VirtualMachineInstance($virtualMachine)
{
	if (Get-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name -WarningAction Ignore)
	{
		Write-Information "Removing virtual machine '$($virtualMachine.name)' ... " -NoNewLine $true
		$operation = Remove-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name
	    if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Virtual machine '$($virtualMachine.name)' operation failed." }
		Write-Text "done."
	}
	else
	{
		Write-Information "Removing virtual machine '$($virtualMachine.name)' ... skipped."
	}
}

# Remove virtual machine instance configuration
function Remove-VirtualMachineInstanceConfiguration($virtualMachine, [bool]$removeDisks)
{
	$disks = Get-AzureDisk | ? {$_.AttachedTo.HostedServiceName -eq $virtualMachine.service -and $_.AttachedTo.RoleName -eq $virtualMachine.name} | Sort-Object -Property Label
	$operation = Remove-AzureVM -ServiceName $virtualMachine.Service -Name $virtualMachine.Name
	if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Virtual machine '$($virtualMachine.name)' operation failed." }
	do
	{
		Start-Sleep -Seconds 5
		$disksInUse = Get-AzureDisk | ? {$_.AttachedTo.HostedServiceName -eq $virtualMachine.service -and $_.AttachedTo.RoleName -eq $virtualMachine.name}
	} while ($disksInUse -ne $null -or $disksInUse.Count -gt 0)
    if ($removeDisks)
    {
	    foreach ($disk in $disks)
	    {
		    $operation = $disk | Remove-AzureDisk		
	    }
    }
}

# Start virtual machine instance
function Start-VirtualMachineInstance($virtualMachine)
{
    $vm = Get-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name -WarningAction Ignore
	if ($vm -ne $null -and $vm.PowerState -eq "Stopped")
	{
		Write-Information "Starting virtual machine '$($virtualMachine.name)' ... " -NoNewLine $true
		$operation = Start-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name
	    if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Virtual machine '$($virtualMachine.name)' operation failed." }
		Write-Text "done."
	}
	else
	{
		Write-Information "Starting virtual machine '$($virtualMachine.name)' ... skipped."
	}
}

# Stop virtual machine instance
function Stop-VirtualMachineInstance($virtualMachine, [bool]$stayProvisioned, [bool]$waitUntilStopped)
{
    $vm = Get-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name -WarningAction Ignore
	if ($vm -ne $null -and $vm.PowerState -eq "Started")
	{
		Write-Information "Stopping virtual machine '$($virtualMachine.name)' ... " -NoNewLine $true
		$operation = Stop-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name -StayProvisioned:$stayProvisioned -Force
	    if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Virtual machine '$($virtualMachine.name)' operation failed." }
        if ($waitUntilStopped)
        {
            while ((Get-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name).PowerState -ne "Stopped")
            {
                Sleep 3
            }
        }
		Write-Text "done."
	}
	else
	{
		Write-Information "Stopping virtual machine '$($virtualMachine.name)' ... skipped."
	}
}

# Create virtual machine instance
function Create-VirtualMachineInstance($subscription, $virtualMachine, $image, $diskName, $diskLocation)
{
    # size="ExtraSmall|Small|Medium|Large|ExtraLarge|A5|A6|A7|A8|A9|A10|A11|Basic_A0|Basic_A1|Basic_A2|Basic_A3|Basic_A4|Standard_D1|Standard_D2|Standard_D3|Standard_D4|Standard_D11|Standard_D12|Standard_D13|Standard_D14|Standard_G1|Standard_G2|Standard_G3|Standard_G4|Standard_G5"
	if ($virtualMachine.availabilitySet -eq $null)
	{
		$config = New-AzureVMConfig -Name $virtualMachine.name -InstanceSize $virtualMachine.size -ImageName $image.ImageName -DiskLabel $diskName -MediaLocation $diskLocation
	}
	else
	{
		$config = New-AzureVMConfig -Name $virtualMachine.name -InstanceSize $virtualMachine.size -ImageName $image.ImageName -DiskLabel $diskName -MediaLocation $diskLocation -AvailabilitySetName $virtualMachine.availabilitySet
	}
	$config.OSVirtualHardDisk.DiskName = $diskName
	$config = Add-VirtualMachineInstanceDataDisks $virtualMachine $config
    if ($image.OS -eq "Windows")
    {
	    if ($virtualMachine.joinDomain -eq $null -or $virtualMachine.joinDomain -eq $false)
	    {	
		    $config = Add-AzureProvisioningConfig -VM $config -Windows -AdminUserName $adminUserName -Password $password -TimeZone $virtualMachine.timeZone -NoRDPEndpoint -NoWinRMEndpoint -DisableAutomaticUpdates
	    }
	    else
	    {
		    $config = Add-AzureProvisioningConfig -VM $config -WindowsDomain -AdminUserName $adminUserName -Password $password -JoinDomain $subscription.virtualMachines.dnsDomain -Domain $subscription.virtualMachines.netBiosDomain -DomainUserName $adminUserName -DomainPassword $password -TimeZone $virtualMachine.timeZone -NoRDPEndpoint -NoWinRMEndpoint -DisableAutomaticUpdates
	    }
    }
    elseif ($image.OS -eq "Linux")
    {
        $thumbprint = (Get-PfxCertificate "$($subscription.virtualMachines.sshCertificate).cer").Thumbprint
        if ((Get-AzureCertificate -ServiceName $virtualMachine.service | ? { $_.Thumbprint -eq $thumbprint }) -eq $null)
        {
            $operation = Add-AzureCertificate -ServiceName $virtualMachine.service -CertToDeploy "$($subscription.virtualMachines.sshCertificate).cer"
		    if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Cloud service certificate '$($virtualMachine.service)' operation failed." }
        }
        $sshKey = New-AzureSSHKey -PublicKey –Fingerprint $thumbprint –Path "/home/$adminUserName/.ssh/authorized_keys"
        if ($virtualMachine.noSSHPassword -eq $true)
        {
		    $config = Add-AzureProvisioningConfig -VM $config -Linux -LinuxUser $adminUserName -Password $password -SSHPublicKeys $sshKey -NoSSHEndpoint -NoSSHPassword
        }
        else
        {
            $config = Add-AzureProvisioningConfig -VM $config -Linux -LinuxUser $adminUserName -Password $password -SSHPublicKeys $sshKey -NoSSHEndpoint
        }
    }
    else
    {
        throw "Image operating system '$($image.OS)' is not supported.";
    }
    return $config
}

# Provision virtual machine instance
function Provision-VirtualMachineInstance($subscription, $virtualMachine, $config)
{
	if ($virtualMachine.name.Length -gt 15) { throw "Virtual machine '$($virtualMachine.name)' name length cannot exceed 15 characters." }
    Set-AzureSubscription -SubscriptionName $subscription.name -CurrentStorageAccountName $virtualMachine.disks.storage
    $command = "New-AzureVM -ServiceName `$virtualMachine.service -VMs `$config -WaitForBoot"
    # Check if this is the first Virtual Machine in this service
	if ((Get-AzureVM -ServiceName $virtualMachine.service -WarningAction Ignore) -eq $null)
	{
        # Set Virtual Network
        $command += " -VNetName `$virtualMachine.network.name"
        # Set reserved Public IP (VIP)
        if ($virtualMachine.network.reservedIP -ne $null)
        {
            $command += " –ReservedIPName `$virtualMachine.network.reservedIP"
        }
	}
    # Set Internal Load Balancer (ILB) configuration
    if ($virtualMachine.network.internalLoadBalancer -ne $null)
    {
        $ilb = New-AzureInternalLoadBalancerConfig -InternalLoadBalancerName $virtualMachine.network.internalLoadBalancer.name -SubnetName $virtualMachine.network.subnets.Split(",")[0] -StaticVNetIPAddress $virtualMachine.network.internalLoadBalancer.internalIPAddress
        $command += " -InternalLoadBalancerConfig `$ilb"
    }
    $operation = Invoke-Expression -Command $command
	if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Virtual machine '$($virtualMachine.name)' operation failed." }
}

# Get virtual machine instance last boot up time
function Get-VirtualMachineInstanceLastBootUpTime($uri, $credential, $port)
{
	$session = $null
    $savedErrorActionPreference = $ErrorActionPreference
    $ErrorActionPreference = "SilentlyContinue"
	try
	{
		$session = New-PSSession -ComputerName $uri[0].DnsSafeHost -Credential $credential -Port $port -UseSSL
		$result = Invoke-Command -Session $session -ScriptBlock {
			$wmi = Get-WmiObject -Class Win32_OperatingSystem
			return $wmi.ConvertToDateTime($wmi.LastBootUpTime)
		}		
		return $result
	}
	catch
	{
		return [System.DateTime]::MinValue
	}
	finally
	{
		if ($session -ne $null)
		{
			try
			{
				Remove-PSSession $session
			}
			catch {}
		}
        $ErrorActionPreference = $savedErrorActionPreference
	}
}

# Restart virtual machine instance
function Restart-VirtualMachineInstance($subscription, [string]$virtualMachineService, [string]$virtualMachineName, [bool]$detect, [string]$waitForService, [int]$skipRestarts = 1)
{
    Write-Information "Virtual machine '$virtualMachineName' restarting ... " -NoNewLine $true
    $restartRequired = $true
    $userName = $subscription.virtualMachines.adminUserName
    $password = $subscription.virtualMachines.password
    $netBiosDomain = $subscription.virtualMachines.netBiosDomain
    $uri = Get-AzureWinRMUri -ServiceName $virtualMachineService -Name $virtualMachineName
    $port = Get-VirtualMachineInstanceEndpointPort $subscription $virtualMachineName
    $credential = New-Object System.Management.Automation.PSCredential(($netBiosDomain + "\" + $userName), (ConvertTo-SecureString $password -AsPlainText -Force))
    if ($detect)
    {
        $session = New-PSSession -ComputerName $uri[0].DnsSafeHost -Credential $credential -Port $port -UseSSL
	    $restartRequired = Invoke-Command -Session $session -ScriptBlock {
		    if ((Get-Item "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations" -ErrorAction SilentlyContinue) -ne $null) { return $true }
		    if ((Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -ErrorAction SilentlyContinue) -ne $null) { return $true }
		    if ((Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -ErrorAction SilentlyContinue) -ne $null) { return $true }
		    return $false
	    }
	    Remove-PSSession $session
    }
    if ($restartRequired)
    {
        for ($i = 0; $i -lt $skipRestarts; $i++)
        {
	        $originalLastBootUpTime = Get-VirtualMachineInstanceLastBootUpTime $uri $credential $port
	        $operation = Restart-AzureVM -ServiceName $virtualMachineService -Name $virtualMachineName
	        if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Virtual machine '$virtualMachineName' restart failed." }
	        do
	        {
		        Start-Sleep 3
		        $currentLastBootUpTime = Get-VirtualMachineInstanceLastBootUpTime $uri $credential $port
	        } while ($currentLastBootUpTime -le $originalLastBootUpTime)
        }
        if (!([string]::IsNullOrEmpty($waitForService)))
	    {
		    $session = New-PSSession -ComputerName $uri[0].DnsSafeHost -Credential $credential -Port $port -UseSSL
		    do
		    {
			    Start-Sleep 3
			    $serviceStatus = Invoke-Command -Session $session -ScriptBlock {
				    param([string]$serviceName)
				    return (Get-Service -Name $serviceName).Status.ToString()
			    } -ArgumentList $waitForService
		    } while ($serviceStatus -ne "Running")
		    Remove-PSSession $session
	    }
		Write-Text "done."
    }
	else
	{
		Write-Text "skipped."
	}
}

# Check if virtual machine instance operation completed successfully
function Test-VirtualMachineInstanceCompleted($subscription, $virtualMachine)
{
    if ($skipCompleted)
    {
        $subscriptionNode = $Global:CompletedOperations.configuration.subscriptions.subscription | ? { $_.name -eq $subscription.name }
        if ($subscriptionNode -ne $null)
        {
            $virtualMachineNode = $subscriptionNode.virtualMachines.virtualMachine | ? { $_.name -eq $virtualMachine.name }
            if ($virtualMachineNode -ne $null)
            {
                return $true
            }
        }
    }
    return $false
}

# Log virtual machine instance completed operation
function Add-VirtualMachineInstanceCompleted($subscription, $virtualMachine)
{
    if ($skipCompleted)
    {
        $subscriptionNode = $Global:CompletedOperations.configuration.subscriptions.subscription | ? { $_.name -eq $subscription.name }
        if ($subscriptionNode -eq $null)
        {
            $subscriptionsNode = $Global:CompletedOperations.configuration.subscriptions
            if ($subscriptionsNode.GetType().Name -eq "String")
            {
                $subscriptionsNode = Get-XmlNode $Global:CompletedOperations $Global:CompletedOperations.configuration $Global:CompletedOperations.configuration.subscriptions "subscriptions"
            }
			$subscriptionNode = $Global:CompletedOperations.CreateElement("subscription", $Global:CompletedOperations.DocumentElement.NamespaceURI)
			$attribute = $Global:CompletedOperations.CreateAttribute("name")
			$attribute.Value = $subscription.name
			$subscriptionNode.Attributes.Append($attribute) | Out-Null
			$subscriptionsNode.AppendChild($subscriptionNode) | Out-Null
        }
        $virtualMachinesNode = $subscriptionNode.virtualMachines
        if (($virtualMachinesNode -ne $null) -and ($virtualMachinesNode.GetType().Name -eq "String"))
        {
            $subscriptionNode.ChildNodes | % { $subscriptionNode.RemoveChild($_) | Out-Null }
        }
        $virtualMachinesNode = Get-XmlNode $Global:CompletedOperations $subscriptionNode $subscriptionNode.virtualMachines "virtualMachines"
		$virtualMachineNode = $Global:CompletedOperations.CreateElement("virtualMachine", $Global:CompletedOperations.DocumentElement.NamespaceURI)
		$attribute = $Global:CompletedOperations.CreateAttribute("name")
		$attribute.Value = $virtualMachine.name
		$virtualMachineNode.Attributes.Append($attribute) | Out-Null
		$virtualMachinesNode.AppendChild($virtualMachineNode) | Out-Null
        $Global:CompletedOperations.Save($CompletedOperationsFile)
    }
}

# Return virtual machine image name
function Get-VirtualMachineInstanceImage($virtualMachine)
{
	if ($virtualMachine.publishDate -eq $null)
	{
		return (Get-AzureVMImage | ? {$_.ImageFamily -eq $virtualMachine.imageFamily} | sort PublishedDate -Descending)[0]
	}
	else
	{
		$publishDate = [DateTime]::Parse($virtualMachine.publishDate)
		return (Get-AzureVMImage | ? {$_.ImageFamily -eq $virtualMachine.imageFamily -and $_.PublishedDate -eq $publishDate})[0]
	}
}

# Add virtual machine instance network
function Add-VirtualMachineInstanceNetwork($virtualMachine, $config)
{
	foreach ($interface in $virtualMachine.network.interfaces.interface)
	{
        if ($interface.internalIPAddress -ne $null)
        {
            Add-AzureNetworkInterfaceConfig -VM $config -Name $interface.name -SubnetName $interface.subnet -StaticVNetIPAddress $interface.internalIPAddress | Out-Null
        }
        else
        {
            Add-AzureNetworkInterfaceConfig -VM $config -Name $interface.name -SubnetName $interface.subnet | Out-Null
        }
    }
    Set-AzureSubnet -VM $config -SubnetNames $virtualMachine.network.subnets.Split(",") | Out-Null
    if ($virtualMachine.network.internalIPAddress -ne $null)
    {
        Set-AzureStaticVNetIP -VM $config -IPAddress $virtualMachine.network.internalIPAddress | Out-Null
    }
    if ($virtualMachine.network.publicIP -ne $null)
    {
        Set-AzurePublicIP -VM $config -PublicIPName $virtualMachine.network.publicIP | Out-Null
    }
}

# Associate virtual machine network security group
function Associate-VirtualMachineNetworkSecurityGroup($virtualMachine)
{
    if ($virtualMachine.network.networkSecurityGroup -ne $null)
    {
	    Write-Information "Virtual machine '$($virtualMachine.name)' associating network security group '$($virtualMachine.network.networkSecurityGroup)' ... " -NoNewLine $true
        $vm = Get-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name
        Set-AzureNetworkSecurityGroupConfig -VM $vm -NetworkSecurityGroupName $virtualMachine.network.networkSecurityGroup | Out-Null
        $operation = $vm | Update-AzureVM
		if (!$? -or $operation.OperationStatus -ne "Succeeded") { throw "Network security group '$($virtualMachine.network.networkSecurityGroup)' operation failed." }
        Write-Text "done."
    }
}

# Assign virtual machine instance resource group
function Assign-VirtualMachineInstanceResourceGroup($virtualMachine)
{
    if ($virtualMachine.resourceGroup -ne $null)
    {
        # Set cloud service resource group
		Write-Information "Virtual machine '$($virtualMachine.name)' cloud service setting resource group '$($virtualMachine.resourceGroup)' ... " -NoNewLine $true
	    if (Move-ResourceItem $virtualMachine.service "Microsoft.ClassicCompute/domainNames" $virtualMachine.resourceGroup)
	    {
		    Write-Text "done."
	    }
	    else
	    {
		    Write-Text "skipped."
	    }
        # Set virtual machine resource group
		Write-Information "Virtual machine '$($virtualMachine.name)' setting resource group '$($virtualMachine.resourceGroup)' ... " -NoNewLine $true
	    if (Move-ResourceItem $virtualMachine.name "Microsoft.ClassicCompute/virtualMachines" $virtualMachine.resourceGroup)
	    {
		    Write-Text "done."
	    }
	    else
	    {
		    Write-Text "skipped."
	    }
    }
}

# Return virtual machine disk name
function Get-VirtualMachineInstanceDiskName($virtualMachine, $lun)
{
	return $virtualMachine.service + "-" + $virtualMachine.name + "-" + $lun
}

# Return virtual machine disk location
function Get-VirtualMachineInstanceDiskLocation($element, $diskName)
{
    return "https://" + $element.storage + ".blob.core.windows.net/" + $element.container + "/" + $diskName + ".vhd"
}

# Add virtual machine instance data disks
function Add-VirtualMachineInstanceDataDisks($virtualMachine, $config)
{
	foreach ($disk in $virtualMachine.disks.disk)
	{
		$diskName = Get-VirtualMachineInstanceDiskName $virtualMachine $disk.lun
        $diskLocation = Get-VirtualMachineInstanceDiskLocation $disk $diskName
		$disk = Add-AzureDataDisk -CreateNew -VM $config -DiskSizeInGB $disk.sizeInGB -DiskLabel $diskName -LUN $disk.lun -HostCaching $disk.hostCaching -MediaLocation $diskLocation
	}
	return $config
}

# Remove virtual machine instance disks
function Remove-VirtualMachineInstanceDisks($virtualMachine)
{
    if (Get-AzureDisk | ? {$_.Label -like "$($virtualMachine.service)-$($virtualMachine.name)-*"})
	{
		Write-Information "Removing virtual machine '$($virtualMachine.name)' disks ... " -NoNewLine $true
		# Wait for disks to be detached
		while ((Get-AzureDisk | ? {$_.Label -like "$($virtualMachine.service)-$($virtualMachine.name)-*" -and $_.AttachedTo -ne $null}) -ne $null) {Start-Sleep 15}
		Get-AzureDisk | ? {$_.Label -like "$($virtualMachine.service)-$($virtualMachine.name)-*"} | Remove-AzureDisk -DeleteVHD | Out-Null
		Write-Text "done."
	}
	else
	{
		Write-Information "Removing virtual machine '$($virtualMachine.name)' disks ... skipped."
	}
}

# Attach Windows virtual machine instance disks
function Attach-VirtualMachineInstanceDisksWindows($virtualMachine, $adminUserName, $password)
{
	Write-Information "Virtual machine '$($virtualMachine.name)' attaching disks ... " -NoNewLine $true
	$uri = Get-AzureWinRMUri -ServiceName $virtualMachine.service -Name $virtualMachine.name
	$credential = New-Object System.Management.Automation.PSCredential($adminUserName, (ConvertTo-SecureString $password -AsPlainText -Force))
	$session = New-PSSession -ComputerName $uri[0].DnsSafeHost -Credential $credential -Port (Get-VirtualMachineInstanceEndpointPort $subscription $virtualMachine.name) -UseSSL
	foreach ($disk in $virtualMachine.disks.disk)
	{
		Invoke-Command -Session $session -ScriptBlock {
			param([string]$diskLun, [string]$driveLetter)
			Set-ExecutionPolicy Unrestricted -Force
			# Initialize & format drive
			$script = "select disk $diskLun`r`n" +
						"online disk noerr`r`n" +
						"attributes disk clear readonly noerr`r`n" +
						"create partition primary noerr`r`n" +
						"format quick"
			$file = "$Env:TEMP\DiskPart.txt"
			Out-File -InputObject $script -FilePath $file -Encoding ASCII
			DiskPart.exe /s $file | Out-Null
			Remove-Item $file
			# Mount drive
			$volume = Get-WmiObject Win32_Volume | ? {!$_.BootVolume -and !$_.SystemVolume -and $_.DriveType -eq "3" -and $_.DriveLetter -eq $null}
			MOUNTVOL.exe ($driveLetter + ":") $volume.DeviceID | Out-Null
		} -ArgumentList $disk.lun, $disk.drive
	}
	# Set drive labels
	Invoke-Command -Session $session -ScriptBlock {
		foreach ($drive in Get-WmiObject Win32_Volume)
		{
			$drive.Label = $drive.DriveLetter.Chars(0) + "-DRIVE"
			$drive.put() | Out-Null
		}
	}
	Remove-PSSession $session
	Write-Text "done."
}

# Attach Linux virtual machine instance disks
function Attach-VirtualMachineInstanceDisksLinux($subscription, $virtualMachine)
{
	Write-Information "Virtual machine '$($virtualMachine.name)' attaching disks ... " -NoNewLine $true
	foreach ($disk in $virtualMachine.disks.disk)
	{
        $content = Get-Content -Path "$($Env:dp0)\Modules\VM\IC.Azure.Disk.Remote.sh" | Out-String
        $content = $content.Replace("[DEVICE_NAME]", $disk.device)
        $content = $content.Replace("[MOUNT_POINT]", $disk.mountPoint)
        $content = $content.Replace("`r`n", "`n")
        [System.IO.File]::WriteAllText("$Env:TEMP\IC.Azure.Disk.Remote.sh", $content.Substring(0, $content.Length - 1))
        Upload-SSHFile $subscription $virtualMachine.service $virtualMachine.name "$Env:TEMP\IC.Azure.Disk.Remote.sh" "/home/$($subscription.virtualMachines.adminUserName)/"
        Execute-RemoteSSHScript $subscription $virtualMachine.service $virtualMachine.name "IC.Azure.Disk.Local.sh" "Virtual machine disk '$($disk.device)' configuration failed."
        Remove-Item "$Env:TEMP\IC.Azure.Disk.Remote.sh"
	}
	Write-Text "done."
}

# Add virtual machine instance endpoints
function Add-VirtualMachineInstanceEndpoints($virtualMachine, $config)
{
	foreach ($endpoint in $virtualMachine.endPoints.endpoint)
	{
        $command = "Add-AzureEndpoint -VM `$config -Name `$endpoint.name -Protocol `$endpoint.protocol -PublicPort `$endpoint.publicPort -LocalPort `$endpoint.localPort"
        if ($endpoint.acls.acl -ne $null)
        {
            $aclConfig = New-AzureAclConfig
            foreach ($acl in $endpoint.acls.acl)
            {
                Set-AzureAclConfig -AddRule -ACL $aclConfig -Order $acl.order -Action $acl.action -RemoteSubnet $acl.remoteSubnet -Description $acl.description | Out-Null
            }
			$command += " -ACL `$aclConfig"
        }
		if ($endpoint.directServerReturn -ne $null)
        {
            [bool]$directServerReturn = [System.Boolean]::Parse($endpoint.directServerReturn)
            $command += " -DirectServerReturn `$directServerReturn"
        }
		if ($endpoint.lbSetName -ne $null)
		{
			$command += " -LBSetName `$endpoint.lbSetName -ProbeProtocol `$endpoint.probeProtocol -ProbePort `$endpoint.probePort -ProbeIntervalInSeconds `$endpoint.probeInterval"
			if ($endpoint.probePath -ne $null)
			{
			    $command += " -ProbePath `$endpoint.probePath"
			}
            if ($endpoint.internalLoadBalancer -ne $null)
            {
			    $command += " -InternalLoadBalancerName `$endpoint.internalLoadBalancer"
            }
		}
        $command += " | Out-Null"
        Invoke-Expression -Command $command
	}
}

# Return virtual machine instance endpoint port
function Get-VirtualMachineInstanceEndpointPort($subscription, $virtualMachineName, [string]$name = "WinRmHTTPs")
{
	return (($subscription.virtualMachines.virtualMachine | ? {$_.name -eq $virtualMachineName}).endPoints.endPoint | ? {$_.name -eq $name}).publicPort
}

# Add virtual machine instance WinRM certificate
function Add-VirtualMachineInstanceWinRMCertificate($virtualMachine)
{
	Write-Information "Virtual machine '$($virtualMachine.name)' installing WinRM certificate ... " -NoNewLine $true
	$thumbprint = (Get-AzureVM -ServiceName $virtualMachine.service -Name $virtualMachine.name | select -ExpandProperty vm).DefaultWinRMCertificateThumbprint
    if (!(Test-Path Cert:\LocalMachine\Root\$thumbprint))
    {
	    $certificate = Get-AzureCertificate -ServiceName $virtualMachine.service -Thumbprint $thumbprint -ThumbprintAlgorithm sha1
	    $file = [IO.Path]::GetTempFileName()
	    $certificate.Data | Out-File $file
	    $certificateFile = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $file
	    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "Root", "LocalMachine"
	    $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
	    $store.Add($certificateFile)
	    $store.Close()
	    Remove-Item $file
    }
	Write-Text "done."
}

# Remove virtual machine instance WinRM certificate
function Remove-VirtualMachineInstanceWinRMCertificate($virtualMachine)
{
	if (Get-ChildItem Cert:\LocalMachine\Root | ? {$_.Subject -eq "CN=$($virtualMachine.service).cloudapp.net"})
	{
		Write-Information "Removing virtual machine '$($virtualMachine.name)' WinRM certificate ... " -NoNewLine $true
		Get-ChildItem Cert:\LocalMachine\Root | ? {$_.Subject -eq "CN=$($virtualMachine.service).cloudapp.net"} | Remove-Item
		if (!$?) { throw "Virtual machine '$($virtualMachine.name)' WinRM certificate operation failed." }
		Write-Text "done."
	}
	else
	{
		Write-Information "Removing virtual machine '$($virtualMachine.name)' WinRM certificate ... skipped."
	}
}

# Add virtual machine instance PuTTY session
function Add-VirtualMachineInstancePuttySession($subscription, $virtualMachine)
{
	Write-Information "Virtual machine '$($virtualMachine.name)' creating PuTTY session ... " -NoNewLine $true
    $session = Get-VirtualMachineInstanceSSHSession -Subscription $subscription -VirtualMachineService $virtualMachine.service -VirtualMachineName $virtualMachine.name -IncludeBatch $false -CommandText "echo `"Session configured.`""
    $processInfo = New-Object System.Diagnostics.ProcessStartInfo
    $processInfo.FileName = "C:\Program Files\PuTTY\PLINK.EXE"
    $processInfo.Arguments = $session
    $processInfo.UseShellExecute = $false
    $processInfo.CreateNoWindow = $true
    $processInfo.WorkingDirectory = $Env:dp0
    $processInfo.RedirectStandardError = $true
    $processInfo.RedirectStandardInput = $true
    $process = [System.Diagnostics.Process]::Start($processInfo)
    $process.StandardInput.Write("y")
    $process.WaitForExit()
    if ($process.ExitCode -ne 0)
    {
        throw [string]::Format("Putty session configuration failed.`r`n{0}", $process.StandardError.ReadToEnd())
    }
	Write-Text "done."
}

# Remove virtual machine instance PuTTY session
function Remove-VirtualMachineInstancePuttySession($subscription, $virtualMachine)
{
	if (Test-Path -Path HKCU:\Software\SimonTatham\PuTTY\SshHostKeys -PathType Container)
	{
        $name = "rsa2@$(Get-VirtualMachineInstanceEndpointPort $subscription $virtualMachine.name "SSH"):$($virtualMachine.service).cloudapp.net"
        if ((Get-ItemProperty -Path HKCU:\Software\SimonTatham\PuTTY\SshHostKeys -Name $name -ErrorAction SilentlyContinue) -ne $null)
        {
		    Write-Information "Removing virtual machine '$($virtualMachine.name)' PuTTY session ... " -NoNewLine $true
            Remove-ItemProperty -Path HKCU:\Software\SimonTatham\PuTTY\SshHostKeys -Name $name -Force
		    Write-Text "done."
	    }
	    else
	    {
		    Write-Information "Removing virtual machine '$($virtualMachine.name)' PuTTY session ... skipped."
	    }
    }
}

# Return virtual machine instance remote PowerShell session
function Get-VirtualMachineInstancePowerShellSession([System.Xml.XmlElement]$Subscription, [string]$VirtualMachineName, $ForceDomainLogin, [switch]$AllowNetworkConnections)
{
    $userName = $Subscription.virtualMachines.adminUserName
    $password = $Subscription.virtualMachines.password
    $netBiosDomain = $Subscription.virtualMachines.netBiosDomain
    $uri = Get-AzureWinRMUri -ServiceName $virtualMachineService -Name $VirtualMachineName
    if (($ForceDomainLogin -eq $true) -or (($Subscription.virtualMachines.virtualMachine | ? {$_.name -eq $VirtualMachineName}).joinDomain -eq $true))
    {
        $credential = New-Object System.Management.Automation.PSCredential(($netBiosDomain + "\" + $userName), (ConvertTo-SecureString $password -AsPlainText -Force))
        $domainJoined = $true
    }
    else
    {
        $credential = New-Object System.Management.Automation.PSCredential($userName, (ConvertTo-SecureString $password -AsPlainText -Force))
        $domainJoined = $false
    }
    $port = Get-VirtualMachineInstanceEndpointPort $Subscription $VirtualMachineName
    if ($AllowNetworkConnections -and $domainJoined)
    {
        return New-PSSession -ComputerName $uri[0].DnsSafeHost -Credential $credential -Port $port -UseSSL -Authentication Credssp
    }
    else
    {
        return New-PSSession -ComputerName $uri[0].DnsSafeHost -Credential $credential -Port $port -UseSSL
    }
}

# Return virtual machine instance remote SSH session
function Get-VirtualMachineInstanceSSHSession([System.Xml.XmlElement]$Subscription, [string]$VirtualMachineService, [string]$VirtualMachineName, [string]$ScriptFile, [bool]$IncludeBatch = $true, [string]$CommandText)
{
    $userName = $Subscription.virtualMachines.adminUserName
    $port = Get-VirtualMachineInstanceEndpointPort $Subscription $VirtualMachineName "SSH"
    $result = "-ssh -P $port -i `"$($subscription.virtualMachines.sshCertificate).ppk`""
    if ($IncludeBatch)
    {
        $result += " -batch"
    }
    if (![string]::IsNullOrEmpty($ScriptFile))
    {
        $result += " -m `"$($Env:dp0)\Modules\VM\$ScriptFile`""
    }
    $result += " $userName@$VirtualMachineService.cloudapp.net"
    if (![string]::IsNullOrEmpty($CommandText))
    {
        $result += " $CommandText"
    }
    return $result
}

# Execute remote SSH script
function Execute-RemoteSSHScript([System.Xml.XmlElement]$Subscription, [string]$VirtualMachineService, [string]$VirtualMachineName, [string]$ScriptFile, [string]$ErrorText)
{
    $session = Get-VirtualMachineInstanceSSHSession $subscription $virtualMachineService $virtualMachineName $ScriptFile
    if ((Start-Process -FilePath "C:\Program Files\PuTTY\PLINK.EXE" -ArgumentList $session -RedirectStandardError "$Env:TEMP\IC.Azure.Error.txt" -RedirectStandardOutput "$Env:TEMP\IC.Azure.Output.txt" -PassThru -Wait -WindowStyle Hidden).ExitCode -ne 0)
    {
        throw "$ErrorText`r`n$(Get-Content "$Env:TEMP\IC.Azure.Error.txt" | Out-String)"
    }
}

# Upload SSH file
function Upload-SSHFile([System.Xml.XmlElement]$Subscription, [string]$VirtualMachineService, [string]$VirtualMachineName, [string]$SourceFile, [string]$TargetFile)
{
    $userName = $Subscription.virtualMachines.adminUserName
    $port = Get-VirtualMachineInstanceEndpointPort $Subscription $VirtualMachineName "SSH"
    $session = "-batch -P $port -i `"$($subscription.virtualMachines.sshCertificate).ppk`" `"$SourceFile`" $userName@$VirtualMachineService.cloudapp.net:$TargetFile"
    if ((Start-Process -FilePath "C:\Program Files\PuTTY\PSCP.EXE" -ArgumentList $session -RedirectStandardError "$Env:TEMP\IC.Azure.Error.txt" -RedirectStandardOutput "$Env:TEMP\IC.Azure.Output.txt" -PassThru -Wait -WindowStyle Hidden).ExitCode -ne 0)
    {
        throw "File '$SourceFile' upload failed."
    }
}

# Execute virtual machine instance configuration scripts
function Execute-VirtualMachineInstanceScripts($subscription, $virtualMachine)
{
	if (!(Should-Run($virtualMachine.scripts))) {return}
	foreach ($script in $virtualMachine.scripts.script)
	{
	    if (!(Should-Run($script))) { continue }
        $target = $virtualMachine
		if ($script.virtualMachine -ne $null)
		{
			$target = $subscription.virtualMachines.virtualMachine | ? {$_.name -eq $script.virtualMachine}
		}
        Invoke-Expression "& `"$($Env:dp0)\Modules\VM\$($script.file)`" $($target.service) $($target.name)"
		if ($script.delay -ne $null)
		{
			Start-Sleep -Seconds $script.delay
		}
	}
}